/*
 * ﻿Copyright (C) 2011-2012 NewMain Softech
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.newmainsoftech.aspectjutil.eventmanager;

import java.lang.reflect.Method;
import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.newmainsoftech.aspectjutil.eventmanager.label.OnEvent;
import com.newmainsoftech.aspectjutil.eventmanager.label.PreEvent;

/** 
 * Container of information of event listener method annotated either {@link OnEvent} or {@link PreEvent}. 
 * Being instantiated for each event class specified by either <code>@OnEvent</code> 
 * or <code>@PreEvent</code> annotations for same event listener method. <br />
 * There shouldn't be necessity of instantiating this class object intentionally.
 * 
 * @author <a href="mailto:artymt@gmail.com">Arata Y.</a>
 */
public class SubscriberMethodUnit {
	protected Logger logger = LoggerFactory.getLogger( this.getClass());
	
	protected final Method method;
	protected final Class<?> methodClass;	// Should not be super class.
	protected final boolean amongInstancesInThread;
	public static final boolean DEFAULT_AMONG_INSTANCES_IN_THREAD_VALUE = true;
	protected final int turn;	// Specify order for execution
	public static final int DEFAULT_TURN_VALUE = 0;
	
	/**
	 * Indicates when this instance is constructed.
	 */
	public final Date timeStamp;

	// Constructors -------------------------------------------------------------------------------
	/**
	 * Constructor should be used to instantiate this for each event what event listener method 
	 * is interested in.
	 * 
	 * @param method Event listener method.
	 * @param methodClass class declaring event listener method
	 * @param amongInstancesInThread value from either {@link PreEvent#amongInstancesInThread()} 
	 * or {@link OnEvent#amongInstancesInThread()}
	 * @param turn specifies order of execution among event listener methods for specific event.
	 * Corresponding to value in array returned by either {@link OnEvent#orderForEachEvent()} 
	 * or {@link OnEvent#amongInstancesInThread()} for specific event that this will be instantiated for.
	 */
	public SubscriberMethodUnit( 
			Method method, Class<?> methodClass, boolean amongInstancesInThread, int turn) {
		this.method = method;
		this.methodClass = methodClass;
		this.amongInstancesInThread = amongInstancesInThread;
		this.turn = turn;
		
		this.timeStamp = new Date();
	}
	// --------------------------------------------------------------------------------------------
	
	// Getters ------------------------------------------------------------------------------------
	/**
	 * Return preserved turn in execution order among event listener method for specific event.
	 * Corresponding to value in array returned by either {@link OnEvent#orderForEachEvent()} 
	 * or {@link OnEvent#amongInstancesInThread()} for specific event that this will be instantiated for.
	 * @return turn in execution order.
	 */
	public final int getTurn() {
		return turn;
	}
	/**
	 * Return <code>boolean</code> value indicating whether event listener method invocation is 
	 * limited only to event triggered within thread what constructed event listener object. 
	 * Should have been preserved from either {@link PreEvent#amongInstancesInThread()} 
	 * or {@link OnEvent#amongInstancesInThread()}. 
	 * @return switch value whether restricts invocation of event listener method to event within 
	 * thread what event listener object has been constructed.
	 */
	public final boolean isAmongInstancesInThread() {
		return amongInstancesInThread;
	}

	/**
	 * Return event listener method
	 * @return event listener method
	 */
	public final Method getMethod() {
		return method;
	}

	/**
	 * Return class declaring event listener method what can be gotten by {@link #getMethod()} 
	 * @return owner class of event listener method
	 */
	public final Class<?> getMethodClass() {
		return methodClass;
	}
	// --------------------------------------------------------------------------------------------

	@Override
	public String toString() {
		String className = this.getClass().getName();
		
		return String.format(
				"%1$s@%2$s{ method: %3$s, methodClass: %4$s, turn: %5$d, " 
				+ "amongInstancesInThread: %6$b, timeStamp: %7$d}",
				className,
				Integer.toHexString( this.hashCode()),
				method.toString(),
				methodClass.toString(),
				turn,
				amongInstancesInThread,
				timeStamp.getTime()
				);
	}
	
	// equals method and hashCode method ----------------------------------------------------------
	/* In order to avoid duplicate instance by multiple class loader:
	 * For equals method, make the following order:
	 * 		methodClass
	 * 		method
	 * 		For this class purpose (used for specific event), turn should not be considered.
	 * For hash method, use methodClass object and method object exclude turn object.
	 */
	/**
	 * Generate hash value for this.
	 * In generating hash code value, the value of next member fields only are used: <br />
	 * <ul>
	 * <li>{@link #method}</li>
	 * <li>{@link #methodClass}</li>
	 * </ul>
	 * Other member fields are not used; this is due to avoid duplicate 
	 * objects by multiple class loaders.  
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((method == null) ? 0 : method.hashCode());
		result = prime * result
				+ ((methodClass == null) ? 0 : methodClass.hashCode());
		return result;
	}
	
	/**
	 * Compare this to <code>obj</code> input.
	 * In comparison, the value of next member fields only are used by following order:<br />
	 * <ol>
	 * <li>{@link #methodClass}</li>
	 * <li>{@link #method}</li>
	 * </ol>
	 * Other member fields are not used in comparison; this is due to avoid duplicate 
	 * objects by multiple class loaders.
	 *   
	 * @param obj being compared with this.
	 */
	@Override
	public boolean equals( Object obj) {
		if (this == obj) return true;
		if (obj == null) return false;
		if ( getClass() != obj.getClass()) return false;
		
		SubscriberMethodUnit other = (SubscriberMethodUnit) obj;
		if (method == null) {
			if (other.method != null)
				return false;
		} 
		else if ( !method.equals( other.getMethod()))
			return false;
		
		if ( methodClass == null) {
			if ( other.methodClass != null)
				return false;
		} 
		else if ( !methodClass.equals( other.getMethodClass()))
			return false;
		
		return true;
	}
	// --------------------------------------------------------------------------------------------
	
}
